﻿// <author>Netmon Cipher Dev Team</author>
// <date>2009-07-16</date>
// <version>1.0</version>
// <summary>This contains methods to generate Netmon Capture File.</summary>

namespace SSLDecryptionExpert
{
    using System;
    using System.Runtime.InteropServices;
    using Microsoft.NetworkMonitor;

    /// <summary>
    /// Class used for writing the decrypted and plain data into new capture file
    /// </summary>
    public static class CaptureGeneration
    {
        #region CaptureFileGeneration

        /// <summary>
        /// This Method is used to write packets in to the capture file.
        /// if application data is encrypted this will be replaced with
        /// the decrypted data.
        /// </summary>
        /// <param name="rawFrame">handle to raw frame</param>
        /// <param name="frameNumber">Frame Number</param>
        /// <param name="frameSource">Source Frame Number</param>
        /// <param name="decryptedFrameNumber">Decrypted Frame Number</param>
        /// <param name="framesInserted">Number of Decrypted Frames Inserted</param>
        /// <param name="isReassembledFrame">Checks if frame is reassembled frame</param>
        /// <param name="isEspPresent">TRUE if Esp is present.</param>
        /// <param name="insertedFrame">Re-assembled Frame</param>
        /// <param name="createdCaptureFile">handle to capturefile</param>
        /// <param name="decryptedApplicationData">decrypted data</param>
        /// <param name="isDecryptedPacket">
        /// Checks if data is decrypted application data.
        /// </param>
        /// <param name="cipherSuiteInfo">
        /// Selected Cipher Suite Information.
        /// </param>
        public static void CaptureFileGeneration(
                                            IntPtr rawFrame,
                                            ref uint frameNumber,
                                            ref uint frameSource,
                                            ref uint decryptedFrameNumber,
                                            ref uint framesInserted,
                                            ref bool isReassembledFrame,
                                            bool isEspPresent,
                                            IntPtr insertedFrame,
                                            IntPtr createdCaptureFile,
                                            byte[] decryptedApplicationData,
                                            bool isDecryptedPacket,
                                            Variables.CipherSuiteInfo cipherSuiteInfo)
        {                       
            byte[] arr;
            byte[] decryptedPayloadHeader;
            byte[] rawData;
            NM_TIME tm;

            if (isDecryptedPacket)
            {
                isDecryptedPacket = false;                
                
                uint rawDataLength;
                NetmonAPI.NmGetRawFrameLength(rawFrame, out rawDataLength);

                uint returnLength;
                rawData = new byte[rawDataLength];
                unsafe
                {
                    fixed (byte* pData = rawData)
                    {
                        NetmonAPI.NmGetRawFrame(rawFrame, rawDataLength, pData, out returnLength);
                    }
                }

                // We'll set the clock back if we don't match
                if (Variables.previousFrameNumber != frameNumber)
                {
                    // Writing Original Frame
                    WriteToCapFile(createdCaptureFile, rawData, rawFrame);
                    
                    Variables.previousFrameNumber = frameNumber;
                    frameSource = decryptedFrameNumber + framesInserted;
                    frameNumber--;
                }

                if (isReassembledFrame)
                {
                    isReassembledFrame = false;

                    byte[] reassembledData;
                    NetmonAPI.NmGetRawFrameLength(insertedFrame, out rawDataLength);

                    reassembledData = new byte[rawDataLength];
                    unsafe
                    {
                        fixed (byte* pData = reassembledData)
                        {
                            NetmonAPI.NmGetRawFrame(
                                    insertedFrame,
                                    rawDataLength,
                                    pData,
                                    out returnLength);
                        }
                    }

                    // Writing reassembled Frame
                    IntPtr reassembledRawFrame = IntPtr.Zero;

                    IntPtr bufferIndex =
                        Marshal.UnsafeAddrOfPinnedArrayElement(reassembledData, 0);

                    tm = new NM_TIME();
                    tm.NmTimeStampSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(NM_TIME));
                    NetmonAPI.NmGetFrameTimeStampEx(rawFrame, ref tm);

                    uint status = NetmonAPI.NmBuildRawFrameFromBufferEx(
                                        bufferIndex,
                                        (uint)reassembledData.Length,
                                        0xFFFC,
                                        ref tm,
                                        out reassembledRawFrame);


                    ////Changing Media Type to 0xFFFC for Re-assembled Frame.
                    //uint status = NetmonAPI.NmBuildRawFrameFromBuffer(
                    //                    bufferIndex,
                    //                    (uint)reassembledData.Length,
                    //                    0xFFFC,
                    //                    0,
                    //                    out reassembledRawFrame);
                    

                    WriteToCapFile(createdCaptureFile, reassembledRawFrame);
                    framesInserted++;
                    frameSource = decryptedFrameNumber + framesInserted;

                    NetmonAPI.NmCloseHandle(reassembledRawFrame);
                }                

                //// Filling Decrypted Payload Header for Decrypted Re-assembled Frame
                FillHeader(frameSource, isEspPresent, out decryptedPayloadHeader);

                if (cipherSuiteInfo.SslVersion.Major == 0)
                {
                    byte[] ssl2Header = BitConverter.GetBytes(
                                            (short)(0x8000 + Variables.multipleApplicationData.Length));

                    Array.Reverse(ssl2Header);

                    //// Fills the frame to be written.
                    arr = AddByteArrays(
                                    decryptedPayloadHeader,
                                    ssl2Header,
                                    Variables.multipleApplicationData);
                }
                else
                {
                    //// Fills SSL Header
                    byte[] sslHeader = FillSslHeader(0x17, cipherSuiteInfo);

                    //// Fills the frame to be written.
                    arr = AddByteArrays(
                                    decryptedPayloadHeader,
                                    sslHeader,
                                    Variables.multipleApplicationData);
                }
                                
                IntPtr rawFrameHandle = IntPtr.Zero;
                IntPtr dataBufferIndex = 
                    Marshal.UnsafeAddrOfPinnedArrayElement(arr, 0);

                tm = new NM_TIME();
                tm.NmTimeStampSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(NM_TIME));
                NetmonAPI.NmGetFrameTimeStampEx(rawFrame, ref tm);

                ////Changing Media Type to 0xFFFA for DecryptedPayloadHeader Frame.
                uint buffstatus = NetmonAPI.NmBuildRawFrameFromBufferEx(
                                    dataBufferIndex,
                                    (uint)arr.Length,
                                    0xFFFA,
                                    ref tm,
                                    out rawFrameHandle);

                WriteToCapFile(createdCaptureFile, rawFrameHandle);

                NetmonAPI.NmCloseHandle(rawFrameHandle);
                
                framesInserted++;
                Variables.decryptedApplicationData = null;
            }
            else if (!isDecryptedPacket && (decryptedApplicationData != null)
                     && !Variables.isClientKeyExchange)
            {
                if (Variables.previousFrameNumber != frameNumber)
                {
                    // Writing Original Frame
                    WriteToCapFile(createdCaptureFile, rawFrame);

                    Variables.previousFrameNumber = frameNumber;
                    frameSource = decryptedFrameNumber + framesInserted;
                    frameNumber--;
                }
                
                //// Fills DecryptedPayloadHeader
                FillHeader(frameSource, isEspPresent, out decryptedPayloadHeader);

                //// Fills SSL Header
                byte[] sslHeader = FillSslHeader(0x16, cipherSuiteInfo);

                //// Fills the frame to be written.
                arr = AddByteArrays(
                                decryptedPayloadHeader,
                                sslHeader,
                                Variables.multipleApplicationData);

                ////Changing Media Type.
                IntPtr rawFrameHandle = IntPtr.Zero;
                IntPtr dataBufferIndex = Marshal.UnsafeAddrOfPinnedArrayElement(arr, 0);

                tm = new NM_TIME();
                tm.NmTimeStampSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(NM_TIME));
                NetmonAPI.NmGetFrameTimeStampEx(rawFrame, ref tm);

                uint buffstatus = NetmonAPI.NmBuildRawFrameFromBufferEx(
                                        dataBufferIndex,
                                        (uint)arr.Length,
                                        0xFFFA,
                                        ref tm,
                                        out rawFrameHandle);

                WriteToCapFile(createdCaptureFile, rawFrameHandle);

                NetmonAPI.NmCloseHandle(rawFrameHandle);

                framesInserted++;
                Variables.decryptedApplicationData = null;                
            }
            else if (Variables.isClientKeyExchange && (Variables.preMasterSecret != null))
            {
                if (Variables.previousFrameNumber != frameNumber)
                {
                    //// Writing Original Frame
                    WriteToCapFile(createdCaptureFile, rawFrame);

                    Variables.previousFrameNumber = frameNumber;
                    frameSource = decryptedFrameNumber + framesInserted;
                    frameNumber--;
                }
                
                //// Fills DecryptedPayloadHeader
                FillHeader(frameSource, isEspPresent, out decryptedPayloadHeader);

                if (cipherSuiteInfo.SslVersion.Major == 0)
                {
                    byte[] ssl2Header = BitConverter.GetBytes(
                                            (short)(0x8000 + Variables.preMasterSecret.Length));

                    Array.Reverse(ssl2Header);

                    //// Fills the frame to be written.
                    arr = AddByteArrays(
                                    decryptedPayloadHeader,
                                    ssl2Header,
                                    Variables.preMasterSecret);
                }
                else
                {
                    //// Fills SSL Header
                    byte[] sslHeader = FillSslHeader(0x16, cipherSuiteInfo);
                    byte[] clientKeyHeader = { 0x10, 0x00, 0x00, 0x30 };

                    byte[] zeroArray = new byte[0];

                    //// Fills the frame to be written.
                    byte[] sslHeaderClient = AddByteArrays(
                                    sslHeader,
                                    clientKeyHeader,
                                    zeroArray);

                    //// Fills the frame to be written.
                    arr = AddByteArrays(
                                    decryptedPayloadHeader,
                                    sslHeaderClient,
                                    Variables.preMasterSecret);
                }

                ////Changing Media Type.
                IntPtr rawFrameHandle = IntPtr.Zero;
                IntPtr dataBufferIndex = Marshal.UnsafeAddrOfPinnedArrayElement(arr, 0);

                tm = new NM_TIME();
                tm.NmTimeStampSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(NM_TIME));
                NetmonAPI.NmGetFrameTimeStampEx(rawFrame, ref tm);

                uint buffstatus = NetmonAPI.NmBuildRawFrameFromBufferEx(
                                        dataBufferIndex,
                                        (uint)arr.Length,
                                        0xFFFA,
                                        ref tm,
                                        out rawFrameHandle);

                WriteToCapFile(createdCaptureFile, rawFrameHandle);

                NetmonAPI.NmCloseHandle(rawFrameHandle);

                framesInserted++;
                Variables.isClientKeyExchange = false;                                
            } // Just a normal unecrypted packet
            else
            {
                if (Variables.previousFrameNumber != frameNumber)
                {
                    WriteToCapFile(createdCaptureFile, rawFrame);
                    if (insertedFrame != IntPtr.Zero)
                    {
                        WriteToCapFile(createdCaptureFile, insertedFrame);
                        framesInserted++;
                    }

                    Variables.decryptedApplicationData = null;
                }
            }
        }

        #endregion

        #region Write to Capture file

        /// <summary>
        /// Writes to the capture file the data present in the byte array.
        /// </summary>
        /// <param name="captureFileHandle">
        /// Handle to the capture file.
        /// </param>
        /// <param name="frameBuff">
        /// Byte array to be written.
        /// </param>
        /// <returns>
        /// Returns status of the operation.
        /// </returns>
        public static uint WriteToCapFile(
                                IntPtr captureFileHandle,
                                byte[] frameBuff,
                                IntPtr oldrawFrame)
        {            
            if (frameBuff == null)
            {
                return 1;
            }

            IntPtr rawFrame = IntPtr.Zero;
            IntPtr dataBufferIndex = Marshal.UnsafeAddrOfPinnedArrayElement(frameBuff, 0);

            NM_TIME tm = new NM_TIME();
            tm.NmTimeStampSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(NM_TIME));
            NetmonAPI.NmGetFrameTimeStampEx(oldrawFrame, ref tm);

            uint buffstatus = NetmonAPI.NmBuildRawFrameFromBufferEx(
                                    dataBufferIndex, 
                                    (uint)frameBuff.Length,
                                    1,
                                    ref tm,
                                    out rawFrame);
            
            if (0 != buffstatus)
            {
                return buffstatus;
            }

            buffstatus = NetmonAPI.NmAddFrame(captureFileHandle, rawFrame);

            NetmonAPI.NmCloseHandle(rawFrame);

            return buffstatus;
        }

        #endregion

        #region Write to Capture file
        
        /// <summary>
        /// Writes to the capture file the data pointed by the rawFrameHandle.
        /// </summary>
        /// <param name="captureFileHandle">
        /// Handle to the capture file.
        /// </param>
        /// <param name="rawFrameHandle">
        /// Pointa to the frame data to be written.
        /// </param>
        /// <returns>
        /// Returns status of the operation.
        /// </returns>
        public static uint WriteToCapFile(
                                IntPtr captureFileHandle,
                                IntPtr rawFrameHandle)
        {
            uint buffstatus = NetmonAPI.NmAddFrame(captureFileHandle, rawFrameHandle);
            return buffstatus;
        }

        #endregion

        #region Find and replace encrypted Data

        /// <summary>
        /// Finds and Replaces a specified number of bytes
        /// with specified bytes.
        /// </summary>
        /// <param name="mainArray">
        /// Array to be searched.
        /// </param>
        /// <param name="arrayToFind">
        /// Search Array to match.
        /// </param>
        /// <param name="replacement">
        /// Replacement Array which will replace 
        /// Search Array Contents in mainArray.
        /// </param>
        /// <param name="startIndex">
        /// Index where the match is located.
        /// </param>
        /// <returns>
        /// Returns status of the operation.
        /// </returns>
        public static bool FindAndReplace(
                                    byte[] mainArray,
                                    byte[] arrayToFind,
                                    byte[] replacement,
                                    out int startIndex)
        {
            if (mainArray == null)
            {
                startIndex = -1;
                return false;
            }

            if (arrayToFind == null)
            {
                startIndex = -1;
                return false;
            }

            if (replacement == null)
            {
                startIndex = -1;
                return false;
            }

            bool find = false;
            if (arrayToFind.Length != replacement.Length)
            {
                startIndex = -1;
                return false;
            }

            startIndex = 0;
            for (int i = 0; i < mainArray.Length; i++)
            {
                if (mainArray[i] == arrayToFind[0])
                {
                    find = true;
                    startIndex = i;
                    int k = i;
                    for (int j = 1; j < arrayToFind.Length; j++)
                    {
                        if (++k > mainArray.Length - 1)
                        {
                            return false;
                        }

                        if (mainArray[k] != arrayToFind[j])
                        {
                            find = false;
                            continue;
                        }
                    }
                }

                if (true == find)
                {
                    break;
                }
            }

            if (find)
            {
                for (int i = 0; i < replacement.Length; i++)
                {
                    mainArray[startIndex + i] = replacement[i];
                }
            }

            return find;
        }

        #endregion

        #region Fill Decrypted Payload Header

        /// <summary>
        /// Fills the DecryptedPayloadHeader.
        /// </summary>
        /// <param name="sourceFrame">Source Frame Number</param>
        /// <param name="isEspPresent">TRUE if Esp is Present</param>
        /// <param name="header">Returns filled header as an out parameter.</param>
        public static void FillHeader(
                                uint sourceFrame,
                                bool isEspPresent,
                                out byte[] header)
        {
            byte[] blobVersion = new byte[2] { 0x00, 0x01 };

            byte[] errorStatus;
            if (Variables.isMacVerified == true)
            {
                errorStatus = new byte[2] { 0x00, 0x00 };
            }
            else
            {
                errorStatus = new byte[2] { 0x00, 0x01 };
            }

            byte[] frameCount = new byte[2] { 0x00, 0x01 };
            byte[] frameSource = new byte[((Convert.ToInt16(frameCount[0]) * 256) +
                                           Convert.ToInt16(frameCount[1])) * 2];

            //// TODO: Presently works only for framecount == 1            
            frameSource = BitConverter.GetBytes((ushort)sourceFrame);
            Array.Reverse(frameSource);

            bool res;
            byte[] sourceIP;
            byte[] destinationIP;
            if (Variables.isSourceClient)
            {
                res = Utils.GetIpFromString(Variables.clientIP, out sourceIP);
                res = Utils.GetIpFromString(Variables.serverIP, out destinationIP);
            }
            else
            {
                res = Utils.GetIpFromString(Variables.clientIP, out destinationIP);
                res = Utils.GetIpFromString(Variables.serverIP, out sourceIP);
            }

            byte[] espPresent = new byte[1];
            espPresent[0] = (true == isEspPresent) ? (byte)1 : (byte)0;

            byte[] versionIP = new byte[1];
            versionIP[0] = (sourceIP.Length == 4) ? (byte)4 : (byte)6;
                        
            byte[] sourcePort = new byte[8];
            sourcePort = Variables.sourcePort;

            byte[] destinationPort = new byte[8];
            destinationPort = Variables.destinationPort;

            byte[] tcpSequenceNumber;
            byte[] tcpNextSequenceNumber;

            if (Variables.isSourceClient)
            {
                tcpSequenceNumber = BitConverter.GetBytes(Variables.clientTcpSequenceNumber);

                if (Variables.isClientKeyExchange)
                {
                    Variables.clientTcpSequenceNumber += (ulong)Variables.preMasterSecret.Length;
                    tcpNextSequenceNumber = BitConverter.GetBytes(Variables.clientTcpSequenceNumber);
                }
                else
                {
                    Variables.clientTcpSequenceNumber += (ulong)Variables.multipleApplicationData.Length;
                    tcpNextSequenceNumber = BitConverter.GetBytes(Variables.clientTcpSequenceNumber);
                }
            }
            else
            {
                tcpSequenceNumber = BitConverter.GetBytes(Variables.serverTcpSequenceNumber);

                Variables.serverTcpSequenceNumber += (ulong)Variables.multipleApplicationData.Length;
                tcpNextSequenceNumber = BitConverter.GetBytes(Variables.serverTcpSequenceNumber);
            }

            Array.Reverse(tcpSequenceNumber);
            Array.Reverse(tcpNextSequenceNumber);

            int headerLength = blobVersion.Length
                                  + 2 ////its own length
                                  + errorStatus.Length
                                  + frameCount.Length
                                  + frameSource.Length
                                  + versionIP.Length
                                  + sourceIP.Length
                                  + destinationIP.Length
                                  + espPresent.Length
                                  + sourcePort.Length
                                  + destinationPort.Length
                                  + tcpSequenceNumber.Length ////Seq Number
                                  + tcpNextSequenceNumber.Length; ////Next Seq Number

            byte[] lengthHeader = BitConverter.GetBytes((ushort)headerLength);
            Array.Reverse(lengthHeader);
            byte[] decryptedPayloadHeader = new byte[0];

            Utils.AppendArray(ref decryptedPayloadHeader, blobVersion);
            Utils.AppendArray(ref decryptedPayloadHeader, lengthHeader);
            Utils.AppendArray(ref decryptedPayloadHeader, errorStatus);
            Utils.AppendArray(ref decryptedPayloadHeader, frameCount);
            Utils.AppendArray(ref decryptedPayloadHeader, frameSource);
            Utils.AppendArray(ref decryptedPayloadHeader, versionIP);
            Utils.AppendArray(ref decryptedPayloadHeader, sourceIP);
            Utils.AppendArray(ref decryptedPayloadHeader, destinationIP);
            Utils.AppendArray(ref decryptedPayloadHeader, espPresent);
            Utils.AppendArray(ref decryptedPayloadHeader, sourcePort);
            Utils.AppendArray(ref decryptedPayloadHeader, destinationPort);
            Utils.AppendArray(ref decryptedPayloadHeader, tcpSequenceNumber);
            Utils.AppendArray(ref decryptedPayloadHeader, tcpNextSequenceNumber);

            header = decryptedPayloadHeader;
        }

        #endregion

        #region Fill SSL Header

        /// <summary>
        /// Fills the SSL Header
        /// </summary>
        /// <param name="contentType">
        /// First Byte of SSL Header
        /// </param>
        /// <param name="cipherSuiteInfo">Cipher Suite Information</param>
        /// <returns>
        /// Returns Filled SSL Header
        /// </returns>
        public static byte[] FillSslHeader(
                            byte contentType,
                            Variables.CipherSuiteInfo cipherSuiteInfo)
        {
            byte[] length;

            if (Variables.isClientKeyExchange)
            {
                length = BitConverter.GetBytes(
                                        (ushort)(Variables.preMasterSecret.Length + 4));
            }
            else
            {
                length = BitConverter.GetBytes(
                                        (ushort)Variables.multipleApplicationData.Length);
            }

            Array.Reverse(length);

            byte[] sslHeader = new byte[3 + length.Length];
            sslHeader[0] = contentType;
            sslHeader[1] = (byte)cipherSuiteInfo.SslVersion.Major;
            sslHeader[2] = (byte)cipherSuiteInfo.SslVersion.Minor;

            Array.Copy(length, 0, sslHeader, 3, length.Length);
            return sslHeader;
        }

        #endregion

        #region Add Byte Arrays

        /// <summary>
        /// Adds three byte arrays in sequence of agrs specified.
        /// </summary>
        /// <param name="decryptedPayloadHeader">
        /// DecryptedPayloadHeader Array
        /// </param>
        /// <param name="sslHeader">
        /// SSL Header Array
        /// </param>
        /// <param name="decryptedData">
        /// Decrypted Data Array
        /// </param>
        /// <returns>
        /// Returns Concatenated Byte Array.
        /// </returns>
        public static byte[] AddByteArrays(
                                byte[] decryptedPayloadHeader,
                                byte[] sslHeader,
                                byte[] decryptedData)
        {
            byte[] arr = new byte[decryptedPayloadHeader.Length 
                                    + sslHeader.Length
                                    + decryptedData.Length];

            Array.Copy(
                    decryptedPayloadHeader,
                    0,
                    arr,
                    0,
                    decryptedPayloadHeader.Length);

            Array.Copy(
                    sslHeader,
                    0,
                    arr,
                    decryptedPayloadHeader.Length,
                    sslHeader.Length);

            Array.Copy(
                    decryptedData,
                    0,
                    arr,
                    decryptedPayloadHeader.Length + sslHeader.Length,
                    decryptedData.Length);

            return arr;        
        }

        #endregion
    }
}
